package cz.acies.uniql.sql.element;

import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Element;
import org.dom4j.dom.DOMElement;


import cz.acies.uniql.dialect.*;
import cz.acies.uniql.metadata.FieldMetaData;
import cz.acies.uniql.metadata.TableMetaData;
import cz.acies.uniql.sql.UniQLExpr;
import cz.acies.uniql.sql.functions.SQLFunc;
import cz.acies.uniql.utils.UniQLConfig;

public class UniQLOrderBy extends UniQLExpr implements Cloneable
{
	private static final long serialVersionUID = -4012771741864968743L;
	
	public static final int ASC = 0;
	public static final int DESC = 1;

	private static HashMap<String, Integer> typeReg = new HashMap<String, Integer>();
	private ArrayList<UniQLOrderByElement> columns;
	
	static {
		typeReg.put("ASC", new Integer(ASC));
		typeReg.put("DESC", new Integer(DESC));
	}

	/**
	 * Konstruktor tridy UniQLOrderBy.
	 */
	public UniQLOrderBy() {
		super();
	}

	/**
	 * Konstruktor tridy UniQLOrderBy.
	 * 
	 * @param dialect - dialect
	 */
	public UniQLOrderBy(Dialect dialect) {
		super(dialect);
	}

	/**
	 * Konstruktor tridy UniQLOrderBy.
	 * Parametr columns je �et�zcov� v�raz obsahuj�c� n�zvy sloupc�, pop��pad� 
	 * Typ ASC | DESC 
	 * nap�. tbl0.IDU, tbl0.HEADER ASC, tbl0.DATE_WRITE DESC  
	 * 
	 * @param coulmns - columns
	 */
	public UniQLOrderBy(String columns) {
		this();
		String cols[] = columns.split("\\,");
		List<String> list = new ArrayList<String>(); 
		for (int i = 0; i < cols.length; i++) {
			String s = cols[i].trim();
			if (s.matches(".*[\\s+|\\)]ASC|asc")) {
				for (String expr : list) {
					addExpr(expr, "ASC");
				}
				String expr = s.replaceAll("[\\s+|\\)]ASC|asc", "");
				addExpr(expr, "ASC");
				list.clear();
			} else if (s.matches(".*[\\s+|\\)]DESC|desc")) {
				for (String expr : list) {
					addExpr(expr, "DESC");
				}
				String expr = s.replaceAll("[\\s+|\\)]DESC|desc", "");
				addExpr(expr, "DESC");
				list.clear();				
			} else {
				list.add(s);
			}
		}
		for (String expr : list) {
			addExpr(expr, "ASC");
		}
	}

	/**
	 * Konstruktor vytvori nonou instanci objektu UniQLOrderBy s vlastnostmi
	 * objektu zadaneho jako parametr konstruktoru.
	 *  
	 * @param orderBy
	 */
	public UniQLOrderBy(UniQLOrderBy expr) throws Exception {
		this();
		UniQLOrderBy clone = (UniQLOrderBy) expr.clone();
		
		setId(clone.getId());
		setName(clone.getName());
		setDialect(clone.getDialect());
		setAliasMap(clone.getAliasMap());

		setColumns(clone.getColumns());
	}
	
	// ----- -----------------------------------------------------------------

	/*
	 * (non-Javadoc)
	 * @see cz.acies.uniql.sql.UniQLExpr#translate()
	 */
	public String translate() throws Exception {
		Dialect dialect = this.getDialect();
		HashMap<String, Object> aliasMap = this.getAliasMap();
		
		StringBuffer sb = new StringBuffer("ORDER BY ");
		if (this.columns != null) {
			for (Iterator<UniQLOrderByElement> i = columns.iterator(); i.hasNext();) {
				UniQLOrderByElement element = i.next();
				int type = element.getType();
				UniQLExpr expr = element.getExpr();
				if (aliasMap != null && !(dialect instanceof MySQLDialect)) {
					if (expr instanceof UniQLColumn) {
						boolean trans = true;
						String table = ((UniQLColumn)expr).getTableName();
						HashMap<String, Object> fromIndex = (HashMap<String, Object>)aliasMap.get("fromIndex");
						HashMap<String, Object> joinIndex = (HashMap<String, Object>)aliasMap.get("joinIndex");
						if (fromIndex != null && fromIndex.containsKey(table)) trans = false;
						else if (joinIndex != null && joinIndex.containsKey(table)) trans = false;
						
						if (!trans) {
							if (table == null) {
							} else {
								String columnName = ((UniQLColumn)expr).getName();
								TableMetaData tbmd = dbmd().getTable(table);
								if (tbmd != null) {
									FieldMetaData fmd = tbmd.getFieldByName(columnName);
									if (fmd != null) {
										int sqltype = fmd.getSqlType();
										switch (sqltype) {
											case Types.LONGVARCHAR:
											case Types.CLOB: {
												UniQLExpr newExpr = new SQLFunc("text_to_varchar").addParam(expr);
												newExpr.setUniQLProperties(this);
												expr = newExpr;
												break;
											}	
										}
									}
								}
							}
						} else {
							String key = expr.getName();
							if (getAliasMap().containsKey(key)) {
								UniQLExpr newExpr = (UniQLExpr) getAliasMap().get(key);
								if (newExpr instanceof UniQLColumn) {
									expr = new UniQLColumn(((UniQLColumn) newExpr).getTableName(), ((UniQLColumn) newExpr).getName());
								} else if (newExpr instanceof UniQLField) {
									UniQLExpr column = ((UniQLField) newExpr).getExpr();
									if (newExpr instanceof UniQLColumn) {
										expr = new UniQLColumn(((UniQLColumn) column).getTableName(), ((UniQLColumn) column).getName());
									}
								}
							}
						}
					}
				} else if (UniQLConfig.DEBUG_MODE && dialect instanceof MySQLDialect) {
					
				}
				expr.setUniQLProperties(this);
				sb.append(expr.translate()).append(" ").append(getTypeString(type));
				if (i.hasNext())
					sb.append(", ");
				else
					sb.append(" ");
			}
		}
		return sb.toString();
	}

	/* (non-Javadoc)
	 * @see cz.acies.uniql.sql.UniQLExpr#clear()
	 */
	public UniQLOrderBy clear() {
		columns.clear();
		columns = null;
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	@Override
	@SuppressWarnings("unchecked")
	public Object clone() throws CloneNotSupportedException {
		//UniQLOrderBy orderBy = new UniQLOrderBy(this.getDialect());
		Object clone = super.clone();
		assert clone instanceof UniQLExpr;
		((UniQLOrderBy) clone).setColumns((ArrayList<UniQLOrderByElement>) columns.clone());
		return clone;
	}

	// ----- -----------------------------------------------------------------
	/**
	 * Pokud je kontejner columns prazdny vraci true.
	 * 
	 * @return - vraci true, pokud je kontejner columns prazdny
	 */
	public boolean isEmpty() {
		return columns.isEmpty();
	}
	
	// ----- -----------------------------------------------------------------
	
	/**
	 * Prida expr.
	 * 
	 * @param expr - expr
	 * 
	 * @return vraci order by
	 */
	public UniQLOrderBy addExpr(UniQLExpr expr) {
		UniQLOrderByElement element = new UniQLOrderByElement();
		element.setExpr(expr);
		element.setType(UniQLOrderBy.ASC);
		if (this.columns == null)
			this.columns = new ArrayList<UniQLOrderByElement>();
		this.columns.add(element);
		return this;
	}

	/**
	 * Prida expr.
	 * 
	 * @param expr - expr
	 * @param type - type
	 * 
	 * @return vraci order by
	 */
	public UniQLOrderBy addExpr(UniQLExpr expr, String type) {
		UniQLOrderByElement element = new UniQLOrderByElement();
		element.setExpr(expr);

		if (type != null && typeReg.containsKey(type.toUpperCase())) {
			element.setType(typeReg.get(type.toUpperCase()));
		} else {
			element.setType(new Integer(UniQLOrderBy.ASC));
		}
		if (this.columns == null)
			this.columns = new ArrayList<UniQLOrderByElement>();
		this.columns.add(element);
		return this;
	}

	/**
	 * Prida objekt typu UniQLExpr. Parametr type je bud ASC nebo DESC
	 * 
	 * @param expr - expr
	 * @param type - type
	 * 
	 * @return vraci objekt UniQLOrderBy
	 */
	public UniQLOrderBy addExpr(String expr, String type) {
		//HashMap<String, Object> map = new HashMap<String, Object>();
		UniQLOrderByElement element = new UniQLOrderByElement();
		UniQLColumn column = new UniQLColumn(expr);
		element.setExpr(column);

		if (type != null && typeReg.containsKey(type.toUpperCase())) {
			element.setType(typeReg.get(type.toUpperCase()));
		} else {
			element.setType(new Integer(UniQLOrderBy.ASC));
		}
		if (this.columns == null)
			this.columns = new ArrayList<UniQLOrderByElement>();
		this.columns.add(element);
		return this;
	}

	/**
	 * Navraci stringove vyjadreni typu objektu.
	 * 
	 * @param type - type
	 * 
	 * @return - vraci stringove vyjadreni typu objektu ("ASC" a "DESC")
	 */
	public String getTypeString(int type) {
		for (Iterator<String> i = typeReg.keySet().iterator(); i.hasNext();) {
			String key = (String) i.next();
			if (((Integer) typeReg.get(key)).intValue() == type)
				return key;
		}
		return "";
	}

	// ----- -----------------------------------------------------------------

	/**
	 * Navraci kontejner columns.
	 * 
	 * @return - vraci kontejner columns (objekt typu ArrayList)
	 */
	public ArrayList<UniQLOrderByElement> getColumns() {
		return columns;
	}

	/**
	 * Nahradi stavajici columns za novoy.
	 * 
	 * @param columns - novy kontejner columns
	 */
	public void setColumns(ArrayList<UniQLOrderByElement> columns) {
		this.columns = columns;
	}

	// ----- -----------------------------------------------------------------

	/*
	 * (non-Javadoc)
	 * @see cz.acies.uniql.sql.UniQLExpr#transToXML()
	 */
	public String transToXML() throws Exception {
		DOMElement node = new DOMElement("UniQLOrderBy");
		transToXML(node);
		return node.asXML();
	}

	/*
	 * (non-Javadoc)
	 * @see cz.acies.uniql.sql.UniQLExpr#transToXML(org.dom4j.dom.DOMElement)
	 */
	@Override
	public DOMElement transToXML(DOMElement node) throws Exception {
		node.setName("UniQLOrderBy");
		if (columns != null) {
			for (Iterator<UniQLOrderByElement> i = columns.iterator(); i.hasNext();) {
				UniQLOrderByElement element = i.next();
				int type = element.getType();
				UniQLExpr expr = element.getExpr();
				if (expr instanceof UniQLExpr) {
					DOMElement param = expr.transToXML(new DOMElement("Criterion"));
					if (param != null) {
						param.setAttribute("type", getTypeString(type));
						node.appendChild(param);
					}
				}
			}
		}
		return node;
	}

	/*
	 * (non-Javadoc)
	 * @see cz.acies.uniql.sql.UniQLExpr#xmlToExpr(java.lang.String)
	 */
	public UniQLExpr xmlToExpr(String xml) throws Exception {
		System.out.println("UniQLOrderBy.xmlToExpr(xml)");
		return null;
	}

	/*
	 * (non-Javadoc)
	 * @see cz.acies.uniql.sql.UniQLExpr#xmlToExpr(org.dom4j.Element)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public UniQLExpr xmlToExpr(Element node) throws Exception {
		ArrayList<Element> params = new ArrayList<Element>(node.elements());
		String name = node.attributeValue("name");
		this.setName(name);
		for (int i = 0; i < params.size(); i++) {
			Element param = (Element) params.get(i);
			String type = param.attributeValue("type");
			String nodename = param.getName();
			UniQLExpr expr = null;
			if (nodename.trim().equals("UniQLColumn")) {
				expr = new UniQLColumn(getDialect()).xmlToExpr(param);
			} else if (nodename.trim().equals("UniQLValue")) {
				expr = new UniQLValue(getDialect()).xmlToExpr(param);
			} else if (nodename.trim().equals("UniQLCondition")) {
				expr = new UniQLCondition(getDialect()).xmlToExpr(param);
			} else if (nodename.trim().equals("SQLFunc")) {
				expr = new SQLFunc(getDialect()).xmlToExpr(param);
			}
			if (expr != null) {
				if (type != null)
					this.addExpr(expr, type);
				else
					this.addExpr(expr);
			}
		}
		return this;
	}
	
	// -----  -----------------------------------------------------------------
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return "UniQLOrderBy( "+this.getColumns()+" )";
	}

	// -----  -----------------------------------------------------------------
}
